package redis.clients.jedis;

import java.util.ArrayList;
import java.util.List;

import redis.clients.jedis.exceptions.JedisDataException;

public class Pipeline extends MultiKeyPipelineBase {

	private MultiResponseBuilder currentMulti;

	private class MultiResponseBuilder extends Builder<List<Object>> {
		private List<Response<?>> responses = new ArrayList<Response<?>>();

		@Override
		public List<Object> build(Object data) {
			@SuppressWarnings("unchecked")
			List<Object> list = (List<Object>) data;
			List<Object> values = new ArrayList<Object>();

			if (list.size() != responses.size()) {
				throw new JedisDataException("Expected data size "
						+ responses.size() + " but was " + list.size());
			}

			for (int i = 0; i < list.size(); i++) {
				Response<?> response = responses.get(i);
				response.set(list.get(i));
				Object builtResponse;
				try {
					builtResponse = response.get();
				} catch (JedisDataException e) {
					builtResponse = e;
				}
				values.add(builtResponse);
			}
			return values;
		}

		public void setResponseDependency(Response<?> dependency) {
			for (Response<?> response : responses) {
				response.setDependency(dependency);
			}
		}

		public void addResponse(Response<?> response) {
			responses.add(response);
		}
	}

	@Override
	protected <T> Response<T> getResponse(Builder<T> builder) {
		if (currentMulti != null) {
			super.getResponse(BuilderFactory.STRING); // Expected QUEUED

			Response<T> lr = new Response<T>(builder);
			currentMulti.addResponse(lr);
			return lr;
		} else {
			return super.getResponse(builder);
		}
	}

	public void setClient(Client client) {
		this.client = client;
	}

	@Override
	protected Client getClient(byte[] key) {
		return client;
	}

	@Override
	protected Client getClient(String key) {
		return client;
	}

	public void clear() {
		if (isInMulti()) {
			discard();
		}

		sync();
	}

	public boolean isInMulti() {
		return currentMulti != null;
	}

	/**
	 * Synchronize pipeline by reading all responses. This operation close the
	 * pipeline. In order to get return values from pipelined commands, capture
	 * the different Response<?> of the commands you execute.
	 */
	public void sync() {
		if (getPipelinedResponseLength() > 0) {
			List<Object> unformatted = client
					.getMany(getPipelinedResponseLength());
			for (Object o : unformatted) {
				generateResponse(o);
			}
		}
	}

	/**
	 * Synchronize pipeline by reading all responses. This operation close the
	 * pipeline. Whenever possible try to avoid using this version and use
	 * Pipeline.sync() as it won't go through all the responses and generate the
	 * right response type (usually it is a waste of time).
	 * 
	 * @return A list of all the responses in the order you executed them.
	 */
	public List<Object> syncAndReturnAll() {
		if (getPipelinedResponseLength() > 0) {
			List<Object> unformatted = client
					.getMany(getPipelinedResponseLength());
			List<Object> formatted = new ArrayList<Object>();
			for (Object o : unformatted) {
				try {
					formatted.add(generateResponse(o).get());
				} catch (JedisDataException e) {
					formatted.add(e);
				}
			}
			return formatted;
		} else {
			return java.util.Collections.<Object> emptyList();
		}
	}

	public Response<String> discard() {
		if (currentMulti == null)
			throw new JedisDataException("DISCARD without MULTI");
		client.discard();
		currentMulti = null;
		return getResponse(BuilderFactory.STRING);
	}

	public Response<List<Object>> exec() {
		if (currentMulti == null)
			throw new JedisDataException("EXEC without MULTI");

		client.exec();
		Response<List<Object>> response = super.getResponse(currentMulti);
		currentMulti.setResponseDependency(response);
		currentMulti = null;
		return response;
	}

	public Response<String> multi() {
		if (currentMulti != null)
			throw new JedisDataException("MULTI calls can not be nested");

		client.multi();
		Response<String> response = getResponse(BuilderFactory.STRING); // Expecting
		// OK
		currentMulti = new MultiResponseBuilder();
		return response;
	}

}
